#include "KeywordProcessor.h"

// 构造函数
KeyWordProcessor::KeyWordProcessor(const string& enStopWordsFile, const string& cnStopWordsFile) 
{
    // 打开英文停用词文件
    ifstream ifsEn{enStopWordsFile};
    if (!ifsEn.is_open()) {
        std::cerr << enStopWordsFile << ": File open failed.\n";
        exit(1);
    }

    // 打开中文停用词文件
    ifstream ifsCn{cnStopWordsFile};
    if (!ifsCn.is_open()) {
        std::cerr << cnStopWordsFile << ": File open failed.\n";
        exit(1);
    }

    // 将停用词存入相应的set数据成员
    string word;
    while (ifsEn >> word) {
        m_enStopWords.insert(word);
    }
    while (ifsCn >> word) {
        m_cnStopWords.insert(word);
    }

    ifsEn.close();
    ifsCn.close();
}


// 外部接口
void KeyWordProcessor::process(const string& enDir, const string& cnDir)
{   
    // 创建英文词典库
    dictMap enDict = create_en_dict(enDir, processOutDir + "enDictionary.txt");
    // 创建英文索引库
    build_en_index(enDict, processOutDir + "enIndex.txt");

    // 创建中文词典库
    dictMap cnDict = create_cn_dict(cnDir, processOutDir + "cnDictionary.txt");
    // 创建中文索引库
    build_cn_index(cnDict, processOutDir + "cnIndex.txt");
}


// 工具函数
void KeyWordProcessor::cleanString(string & str) 
{
    for (auto& ch : str) {
        if (!isalpha(ch)) {
            ch = ' ';
        }
        else if (isupper(ch)) {
            ch = tolower(ch);
        }
    }
}


// 创建英文词典库
dictMap KeyWordProcessor::create_en_dict(const string& enDir, const string& outfile) 
{
    // 1、读取enDir目录下的所有文件名
    vector<string> fileNames;
    try {
        fileNames = DirectoryScanner::scan(enDir);
    } catch (const string& errmsg) {
        std::cerr << enDir  << ": "<< errmsg << std::endl;
        exit(1);
    }
    
    // 2、依次读取每个语料文件，进行词典构建
    dictMap dictionary;   // 词典

    for (const auto& file : fileNames) {
        // 2-1. 拼接目录名和文件名，打开文件
        string filename = enDir + "/" + file;
        ifstream ifs{filename};
        if (!ifs.is_open()) {
            std::cerr << filename << ": File open failed.\n";
            continue;
        }

        // 2-2. 按行读取文件内容
        string line;
        while (getline(ifs, line)) {
            // 将非字母符号替换成空格，将大写字母转为小写
            cleanString(line); 
            // 按空格切取token，并将其加入map，以统计频率
            istringstream iss{line};
            string word;
            while (iss >> word) {
                // 停用词不参与统计
                if (m_enStopWords.find(word) != m_enStopWords.end()) {
                    continue;
                }
                // 成功统计
                dictionary[word] += 1;
            }
        }
        
        ifs.close();
    }

    // 3、将词典导出到文件中
    ofstream ofs{outfile};
    if (!ofs.is_open()) {
        std::cerr << "Output dictionary to file '" << outfile << "' failed\n";
        return dictionary;
    }
    for(const auto &[word, count] : dictionary){
        ofs << word  << " "
            << count << std::endl;
    }
    ofs.close();

    return dictionary;
}


// 创建英文索引库内容
void KeyWordProcessor::build_en_index(dictMap& dict, const string& outfile) 
{   
    // 为词典库建立一个从单词到行号的映射
    map<string, u_long> wordToLineNo;   
    u_long lineNo = 0;
    for (auto &[word, _] : dict) {
        wordToLineNo[word] = ++lineNo;
    }

    // 索引库
    indexMap index;

    // 为26个字母构建索引（n平方复杂度）
    for (char ch = 'a'; ch <= 'z'; ++ch) {
        string key{ch};
        index[key];
        for (auto & [word, line]: wordToLineNo) {
            if (word.find(ch) != string::npos) {
                index[key].insert(line);  // 行号加入对应字母的索引
            }
        }
    }

    // 将索引导出到文件中
    ofstream ofs{outfile};
    if (!ofs.is_open()) {
        std::cerr << "Output index library to file '" << outfile << "' failed\n";
        return; 
    }
    for(const auto &[word, vec] : index){
        ofs << word  << " ";
        for (auto & line : vec) {
            ofs << line << " ";
        }
        ofs << std::endl;
    }
    ofs.close();
}


// 创建中文词典库
dictMap KeyWordProcessor::create_cn_dict(const string& cnDir, const string& outfile) 
{
     // 1、读取cnDir目录下的所有文件名
    vector<string> fileNames;
    try {
        fileNames = DirectoryScanner::scan(cnDir);
    } catch (const string& errmsg) {
        std::cerr << cnDir  << ": "<< errmsg << std::endl;
        exit(1);
    }
    
    // 2、依次读取每个语料文件，进行词典构建
    dictMap dictionary;   // 词典

    for (const auto& file : fileNames) {
        // 2-1. 拼接目录名和文件名，打开文件
        string filename = cnDir + "/" + file;
        ifstream ifs{filename}; 
        if (!ifs.is_open()) {
            std::cerr << filename << ": File open failed.\n";
            continue;
        }

        // 2-2. 一次读取文件内容 (使用迭代器)
        string content(std::istreambuf_iterator<char>{ifs}, 
                       std::istreambuf_iterator<char>{});      

        // 2-3. 使用正则表达式清洗
        // a. 删除掉所有换行符和空格
        content = std::regex_replace(content, std::regex("[\r\n ]+"), "");
        // b. 删掉英文标点符号、英文字母、数字
        content = std::regex_replace(content, std::regex("[[:punct:][:digit:][:alpha:]]"), "");
        // 2-4. 使用jieba分词器进行分词
        std::vector<std::string> words;
        m_tokenizer.Cut(content, words);

        // 2-5. 词频统计
        for (auto & word : words) {
            // 停用词不参与统计
            if (m_cnStopWords.find(word) != m_cnStopWords.end()) {
                continue;
            }
            // 成功统计
            dictionary[word] += 1;
        }

        ifs.close();
    }

    // 3、将词典导出到文件中
    ofstream ofs{outfile};
    if (!ofs.is_open()) {
        std::cerr << "Output dictionary to file '" << outfile << "' failed\n";
        return dictionary;
    }
    for(const auto &[word, count] : dictionary){
        ofs << word  << " "
            << count << std::endl;
    }
    ofs.close();

    return dictionary;
}


// 创建汉字索引库
void KeyWordProcessor::build_cn_index(dictMap& dict, const string& outfile) {
    // 为词典库建立一个从单词到行号的映射
    map<string, u_long> wordToLineNo;   
    u_long lineNo = 0;
    for (auto& [word, _] : dict) {
        wordToLineNo[word] = ++lineNo;
    }

    // 索引库
    indexMap index;

    // 为词典库中出现过的汉字构建索引
    for (auto& [word, line] : wordToLineNo) {
        const char* it = word.c_str();
        const char* end = word.c_str() + word.size();
        // 使用库utf8，依次拆解word中的每个汉字
        while (it != end) {
            auto start = it;
            utf8::next(it, end);    // 将it移动到下一个utf8字符所在的位置
            string sinoChar = string{ start, it };  // 一个汉字需要占用多个字节
            index[sinoChar].insert(line);
        }
    }

    // 将索引导出到文件中
    ofstream ofs{outfile};
    if (!ofs.is_open()) {
        std::cerr << "Output index library to file '" << outfile << "' failed\n";
        return; 
    }
    for(const auto &[word, vec] : index){
        ofs << word  << " ";
        for (auto & line : vec) {
            ofs << line << " ";
        }
        ofs << std::endl;
    }
    ofs.close();
}